cbuffer cbPerObject
{
    float4x4    sys_TransformMatrix;
    float4x4    sys_ModelMatrix;
};

cbuffer cbSkinning
{
    float4      BoneWorldQuats[50*2];
};

cbuffer cbBlinnPhong
{
    float Shininess;
    float Specular;
};

struct VS_IN_static
{
    float3 Vertex : POSITION;
    float3 Normals : NORMAL;
    float2 TexCoord : TEXCOORD; 
};

struct VS_IN_skinning
{
    float3 Vertex : POSITION;
    float3 Normals : NORMAL;
    float2 TexCoord : TEXCOORD;
    float4 BoneID : BONEID;
    float4 VertexWgt : WEIGHT; 
};

struct VS_OUT
{
    float3 Normal : NORMAL;
    float2 TexCoord : TEXCOORD;
    float4 Position : SV_POSITION;
};

struct PS_OUT
{
    float4 Albedo : SV_TARGET0;
    float4 Normal : SV_TARGET1;
};

SamplerState gTextureSampler
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = CLAMP;
    AddressV = CLAMP;
};

Texture2D Albedo;

VS_OUT VS_skinning(VS_IN_skinning vIn)
{
    VS_OUT vOut;
    float4 qnd0 = BoneWorldQuats[2*int(vIn.BoneID.x)],
           qnd1 = BoneWorldQuats[2*int(vIn.BoneID.y)],
           qnd2 = BoneWorldQuats[2*int(vIn.BoneID.z)],
           qnd3 = BoneWorldQuats[2*int(vIn.BoneID.w)],
           qd0 = BoneWorldQuats[2*int(vIn.BoneID.x)+1],
           qd1 = BoneWorldQuats[2*int(vIn.BoneID.y)+1],
           qd2 = BoneWorldQuats[2*int(vIn.BoneID.z)+1],
           qd3 = BoneWorldQuats[2*int(vIn.BoneID.w)+1];
    if(dot(qnd0, qnd1) < 0)
    {
        qnd1 = -qnd1;
        qd1 = -qd1;
    }
    if(dot(qnd0, qnd2) < 0)
    {
        qnd2 = -qnd2;
        qd2 = -qd2;
    }
    if(dot(qnd0, qnd3) < 0)
    {
        qnd3 = -qnd3;
        qd3 = -qd3;
    }
    float4 qnd = qnd0*vIn.VertexWgt.x +
                 qnd1*vIn.VertexWgt.y +
                 qnd2*vIn.VertexWgt.z +
                 qnd3*vIn.VertexWgt.w;
    float4 qd = qd0*vIn.VertexWgt.x +
                qd1*vIn.VertexWgt.y +
                qd2*vIn.VertexWgt.z +
                qd3*vIn.VertexWgt.w;
    float len_non_dual = length(qnd);
    qnd /= len_non_dual;
    qd /= len_non_dual;
    vOut.Position = float4(vIn.Vertex + cross(2.0*qnd.xyz, cross(qnd.xyz, vIn.Vertex) + qnd.w*vIn.Vertex) +
                   2.0*(qnd.w*qd.xyz - qd.w*qnd.xyz + cross(qnd.xyz, qd.xyz)), 1.0);
    vOut.Normal = vIn.Normals + cross(2.0*qnd.xyz, cross(qnd.xyz, vIn.Normals) + qnd.w*vIn.Normals);
    
    vOut.Position = mul(vOut.Position, sys_ModelMatrix);
    vOut.Normal = mul(vOut.Normal, (float3x3)sys_ModelMatrix);
    
    vOut.TexCoord = vIn.TexCoord;
    vOut.Position = mul(vOut.Position, sys_TransformMatrix);
    
    return vOut;
}

VS_OUT VS_static(VS_IN_static vIn)
{
    VS_OUT vOut;

    vOut.Position = mul(float4(vIn.Vertex, 1.0), sys_ModelMatrix);
    vOut.Normal = mul(vIn.Normals, (float3x3)sys_ModelMatrix);
    
    vOut.TexCoord = vIn.TexCoord;
    vOut.Position = mul(vOut.Position, sys_TransformMatrix);
    
    return vOut;
}

PS_OUT PS(VS_OUT pIn)
{
    PS_OUT pOut;
    float3 normal = normalize(pIn.Normal);
    pOut.Albedo = float4(Albedo.Sample(gTextureSampler, pIn.TexCoord).xyz, Specular);
    pOut.Normal = float4(normal.xyz, log2(Shininess)/10.5);
    return pOut;
}

technique10 StaticMesh
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_4_0, VS_static()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, PS()));
    }
}

technique10 SkinnedMesh
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_4_0, VS_skinning()));
        SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_4_0, PS()));
    }
}